
package roborally.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import be.kuleuven.cs.som.annotate.*;
import be.kuleuven.cs.som.taglet.*;

import exception.IllegalPositionException;

/**
 * A Class representing a collection of positions. A range is iterable.
 * 
 * @author Mathieu REYMOND
 * @author Joren VERSPEURT
 * 
 * @version 2.0
 *
 */
public class Range implements Iterable<Position> {
	
	/**
	 * Create a new empty range.
	 */
	public Range() {
		positions = new ArrayList<Position>();
	}
	
	/**
	 * Add a given position to this range.
	 * 
	 * @param position The given position to add.
	 * 
	 * @post ...
	 * 		 | if (position != null && !contains(position))
	 * 		 | then new.contains(position)
	 */
	public void addPosition(Position position){
		if(position != null && !positions.contains(position)) {
			positions.add(position);
		}
	}
	
	/**
	 * Remove a given position from this range.
	 * 
	 * @param position The given position to remove
	 * 
	 * @post ...
	 * 		 | if position != null
	 * 		 | then !new.contains(position)
	 */ 
	public void removePosition(Position position){
		if(position != null) {
			positions.remove(position);
		}
	}
	
	/**
	 * Get the size of this range
	 * 
	 * @return ...
	 * 		   | let 
	 * 		   | 	nb = 0
	 * 		   | in
	 * 		   | 	for each pos in this
	 * 		   | 		nb++
	 * 		   | 	result == nb
	 */
	public int size() {
		return positions.size();
	}

	/**
	 * Check if this range contains a given position
	 * 
	 * @param position The given position to check
	 * 
	 * @return ...
	 * 		   | for each pos in this
	 * 		   | 	if(pos.equals(position))
	 * 		   |	then result == true
	 */
	public boolean contains(Position position) {
		return positions.contains(position);
	}

	/**
	 * Check if this range is empty()
	 */
	@Basic
	public boolean isEmpty() {
		return positions.isEmpty();
	}

	/**
	 * The list of positions of this range
	 */
	private List<Position> positions;

	/**
	 * Get a range with all the common positions of this range and another given range.
	 * 
	 * @param other The other given range.
	 * 
	 * @return ...
	 * 		   | for each position in this
	 * 		   | 	if this.contains(position) && other.contains(position)
	 * 		   |	then result.contains(position)
	 */
	public Range getCommonPositions(Range other) {
		
		Range range = new Range();
		for(Position position : other) {
			if(positions.contains(position)) {
				range.addPosition(position);
			}
		}
		return range;
	}

	/**
	 * Gives a range that represent the edge of this range.
	 * An edge of a range contains all the positions of this range whose 4 neighbors are not all in this range.
	 * 
	 * @return ...
	 * 		   | for each position in this
	 * 		   | 	for each orientation in Orientation.values()
	 * 		   | 		if(!contains(position.getNeighbour(orientation)))
	 * 		   |		then result.contains(position)
	 */
	public Range getEdge(){
		
		Range range = new Range();
		
		for(Position position : positions) {
			boolean allNeighbours = true;
			for(Orientation orientation : Orientation.values()) {
				try {
					if(!positions.contains(position.getNeighbour(orientation))) {
					allNeighbours = false;
				}
				}
				catch(IllegalPositionException e) {
					allNeighbours = false;
				}
				
			}
			if(!allNeighbours) {
				range.addPosition(position);
			}
		}
		return range;
	}
	
	/**
	 * Gives a list that contains all the sets of positions of 2 given ranges where the distance between the positions in the different sets are minimal.
	 * 
	 * @param rangeA The first given range
	 * @param rangeB The second given range
	 * 
	 * @return ...
	 * 		   | for each set in result
	 * 		   | 	for each positionA in rangeA
	 * 		   |		for each positionB in rangeB
	 *		   | 			set.iterator().next().getManhattanDistance(set.iterator().next()) <= positionA.getManhattanDistance(positionB)
	 */
	public static List<Set<Position>> posWithShortestDistanceBetween(Range rangeA, Range rangeB) {
		
		Range common = rangeA.getCommonPositions(rangeB);
		Collection<Set<Position>> pairs= new HashSet<Set<Position>>();
		if(!common.isEmpty()) {
			pairs = new HashSet<Set<Position>>();
			
			for(Position position : common) {
				for(Orientation orientation : Orientation.values()) {
					
					try {
						if(common.contains(position.getNeighbour(orientation))) {
						Set<Position> set = new HashSet<Position>();
						set.add(position);
						set.add(position.getNeighbour(orientation));
						pairs.add(set);
						}
					}
					catch(IllegalPositionException e) {
						
					}
				}
			}
			if(common.size() == 1) {
				Position comPos = common.iterator().next();
				for(Orientation orientation : Orientation.values()) {
					try {
						Position temp = comPos.getNeighbour(orientation);
						if(rangeA.contains(temp) || rangeB.contains(temp)) {
							Set<Position> set = new HashSet<Position>();
							set.add(comPos);
							set.add(temp);
							pairs.add(set);
						}
					}
					catch(IllegalPositionException e) {
						
					}
				}
			}
		}
		else {
			List<Set<Position>> list = new ArrayList<Set<Position>>();
			for(Position positionA : rangeA.getEdge()) {
				for(Position positionB : rangeB.getEdge()) {
					Set<Position> set = new HashSet<Position>();
					if(!list.isEmpty()) {
						Iterator<Position> it = list.get(0).iterator();
						Position[] pos = {it.next(), it.next()};
						
						if(positionA.getManhattanDistance(positionB) < pos[0].getManhattanDistance(pos[1])) {
							list.clear();
						}
						if(positionA.getManhattanDistance(positionB) <= pos[0].getManhattanDistance(pos[1])) {
							set.add(positionA);
							set.add(positionB);
							list.add(set);
						}
					}
					else {
						set.add(positionA);
						set.add(positionB);
						list.add(set);
					}
					
				}
				pairs = list;
			}
		}
		
		return new ArrayList<Set<Position>>(pairs);
	}

	/**
	 * Get the range's iterator.
	 */
	@Override @Basic
	public Iterator<Position> iterator() {

		return positions.iterator();
	}
	
	/**
	 * Gives a string that describes this range.
	 */
	@Override
	public String toString() {
		return positions.toString();
	}
	
	/**
	 * Check if this range equals a given object.
	 * 
	 * @param other The given object to check.
	 * 
	 * @return ...
	 * 		   | if(this.getClass().equals(other.getClass()))
	 * 		   | then 
	 * 		   | 	for each position in this
	 * 		   | 		if (!((Range) other).contains(position))
	 * 		   |		then result == false
	 * 		   | 	for each position in other
	 * 		   |		if (!this.contains(position))
	 * 		   |		then result == false
	 * 		   | else result == false
	 */
	@Override
	public boolean equals(Object other) {
		boolean result = false;
		if(this.getClass().equals(other.getClass())) {
			result = true;
			for(Position position : this) {
				if(!((Range) other).contains(position)) {
					result = false;
				}
			}
			for(Position position : ((Range)other)) {
				if(!this.contains(position)) {
					result = false;
				}
			}
		}
		return result;
	}

}
